package net.sf.fmj.ejmf.toolkit.util;

import javax.media.Controller;
import javax.media.ControllerClosedEvent;
import javax.media.ControllerEvent;
import javax.media.ControllerListener;
import javax.media.Player;
import javax.media.Processor;
import javax.media.StopEvent;
import javax.media.Time;
import javax.media.TransitionEvent;

/**
 * The StateWaiter class provides routines to allow the current thread to wait
 * for a Controller to reach a particular state. A StateWaiter object will first
 * register itself as a ControllerListener on the given Controller object. It
 * will then set the state to be waited for, and block the current thread until
 * either
 * <UL>
 * <LI>the Controller posts a TransitionEvent indicating that it has moved to
 * the desired state, or</LI>
 * <LI>the Controller posts a ControllerErrorEvent, indicating that an error
 * occurred andthe transition failed</LI>
 * </UL>
 * 
 * From the book: Essential JMF, Gordon, Talley (ISBN 0130801046). Used with
 * permission.
 * 
 * @author Steve Talley and Rob Gordon
 */
public class StateWaiter implements ControllerListener {
	/**
	 * The state to be waited for
	 */
	private int state;

	/**
	 * Indicates whether the desired state has been reached
	 */
	private boolean stateReached = false;

	/**
	 * Indicates whether this StateWaiter is already a ControllerListener of its
	 * Controller.
	 */
	private boolean listening = false;

	/**
	 * The Controller on which to wait for the desired state
	 */
	private Controller controller;

	/**
	 * Construct a StateWaiter object for the given Controller
	 * 
	 * @param controller
	 *            the Controller on which to wait for the desired state
	 */
	public StateWaiter(Controller controller) {
		this.controller = controller;
	}

	/**
	 * Sets the state to wait for
	 */
	private void setState(int state) {
		this.state = state;
		stateReached = false;
		addAsListener();
	}

	/**
	 * Adds this StateWaiter as a ControllerListener of the Controller
	 */
	private void addAsListener() {
		if (!listening) {
			controller.addControllerListener(this);
			listening = true;
		}
	}

	/**
	 * Removes this StateWaiter as a ControllerListener of the Controller
	 */
	private void removeAsListener() {
		controller.removeControllerListener(this);
		listening = false;
	}

	/**
	 * Listens for a transition to the state that this StateWaiter is waiting
	 * for. Notifies the the waiting thread and stops listening if any of the
	 * following occur:
	 * <p>
	 * <UL>
	 * <LI>A TransitionEvent to the desired state is posted</LI>
	 * <LI>A StopEvent is posted</LI>
	 * <LI>A ControllerClosedEvent is posted (indicating a failure)</LI>
	 * </UL>
	 * 
	 * @param event
	 *            the media event
	 */
	public synchronized void controllerUpdate(ControllerEvent event) {
		if (event.getSourceController() != controller) {
			return;
		}

		if (event instanceof TransitionEvent) {
			int currState = ((TransitionEvent) event).getCurrentState();

			stateReached = (currState >= state);
		}

		// Is this a stop event or a Controller Error?
		if (event instanceof StopEvent
				|| event instanceof ControllerClosedEvent || stateReached) {
			// Stop listening
			removeAsListener();

			// Notify threads waiting for state change
			notifyAll();
		}
	}

	/**
	 * Calls realize() on the Controller and blocks the current thread until the
	 * Controller is Realized.
	 * 
	 * @return boolean indicating whether the transition was successful.
	 */
	public boolean blockingRealize() {
		setState(Controller.Realized);
		controller.realize();
		return waitForState();
	}

	/**
	 * Calls prefetch() on the Controller and blocks the current thread until
	 * the Controller is Prefetched.
	 * 
	 * @return boolean indicating whether the transition was successful.
	 */
	public boolean blockingPrefetch() {
		setState(Controller.Prefetched);
		controller.prefetch();
		return waitForState();
	}

	/**
	 * Casts the Controller to a Player, calls start(), and blocks the current
	 * thread until the player is Started.
	 * 
	 * @return boolean indicating whether the transition was successful.
	 * 
	 * @exception ClassCastException
	 *                If the Controller is not a Player
	 */
	public boolean blockingStart() {
		setState(Controller.Started);
		Player player = (Player) controller;
		player.start();
		return waitForState();
	}

	/**
	 * Calls syncStart() on the Controller and blocks the current thread until
	 * the Controller is Started. This could throw a ClockStartedError if the
	 * Controller is not in the Prefetched state.
	 * 
	 * @return boolean indicating whether the transition was successful.
	 */
	public boolean blockingSyncStart(Time t) {
		setState(Controller.Started);
		controller.syncStart(t);
		return waitForState();
	}

	public boolean blockingConfigure() {
		setState(Processor.Configured);
		((Processor) controller).configure();
		return waitForState();
	}

	/**
	 * Blocks the current thread until the Controller has reached the previously
	 * specified state or an error has occurred.
	 * 
	 * @return boolean indicating whether the transition was successful.
	 */
	private synchronized boolean waitForState() {
		while (listening) {
			try {
				wait();
			} catch (InterruptedException e) {
			}
		}

		return stateReached;
	}
}
